﻿// ----------------------------------------------------------------------------------
// Sample Code from Fernando Machado Píriz's blog: http://fernandomachadopiriz.com
//
// <copyright company="Fernando Machado Píriz" file="CloseProgramsViewModel.cs">
// Copyright (c) Fernando Machado Píriz. Distributed under FSF's GPL v2.</copyright>
// ----------------------------------------------------------------------------------
// This code and information are provided "as is" without warranty of any kind,
// either expressed of implied, including but not limited to the implied warranties
// of merchantability and/or fitness for a particular purpose.
// ----------------------------------------------------------------------------------
namespace ClosePrograms
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using GivingAPresentation.Commons;
    using Microsoft.Win32;
    
    /// <summary>
    /// The view model of the <code>CloseProgramsView</code> using the Model View View Model pattern. An instance of this class is automatically
    /// created by the view as a window resource.
    /// </summary>
    public class CloseProgramsViewModel : BaseViewModel
    {
        /// <summary>
        /// Backend field for <code>Model</code> property.
        /// </summary>
        private CloseProgramsModel model = new CloseProgramsModel();

        /// <summary>
        /// Backend field for <code>View</code> property.
        /// </summary>
        private CloseProgramsView view;

        /// <summary>
        /// Initializes a new instance of the CloseProgramsViewModel class.
        /// </summary>
        public CloseProgramsViewModel()
        {
            // These three commands are associated with checkboxes in the view. The command is used just to enable or disable the checkbox
            // accordingly to another checkbox's state. Command behavior is provided by default checkbox behavior.
            this.CloseThesePrograms = new RelayCommand(
                argument => this.model.CloseThesePrograms = this.model.CloseThesePrograms,
                result => !this.model.Presenting);

            this.KillIfCannotClose = new RelayCommand(
                argument => this.model.KillIfCannotClose = this.model.KillIfCannotClose,
                result => this.model.CloseThesePrograms);

            this.AskFirst = new RelayCommand(
                argument => this.model.AskFirst = this.model.AskFirst,
                result => this.model.KillIfCannotClose);

            // These three commands are asociated with view's buttons to add and remove programs from the list of programs to close during the
            // presentation.
            this.AddRunningProgram = new RelayCommand(
                argument => this.DoAddRunningProgram(argument as ListBox),
                result => !this.model.Presenting);

            this.AddProgramFromFile = new RelayCommand(
                argument => this.DoAddProgramFromFile(argument as ListBox),
                result => !this.model.Presenting);

            this.RemoveProgram = new RelayCommand(
                argument => this.DoRemoveProgram(argument as ListBox),
                result => !this.model.Presenting);
        }

        /// <summary>
        /// Gets a command to display a dialog box to add one or more running programs to the list of programs to be closed while the user is
        /// giving a presentation.
        /// </summary>
        public ICommand AddRunningProgram { get; private set; }

        /// <summary>
        /// Gets a command to display a dialog box to add one or more programs from disk to the list of programs to be closed while the user is
        /// giving a presentation.
        /// </summary>
        public ICommand AddProgramFromFile { get; private set; }

        /// <summary>
        /// Gets a command to remove one or more selected programs from the list of programs to be closed while the user is giving a
        /// presentation.
        /// </summary>
        public ICommand RemoveProgram { get; private set; }

        /// <summary>
        /// Gets a command to toggle a value indicating if programs in the list should be closed while the user is giving a presentation.
        /// </summary>
        public ICommand CloseThesePrograms { get; private set; }

        /// <summary>
        /// Gets a command to toggle a value indicating whether programs in the list should be killed if they cannot be closed.
        /// </summary>
        public ICommand KillIfCannotClose { get; private set; }

        /// <summary>
        /// Gets a command to toggle a value indicating whether the user should provide a confirmation before killing a program.
        /// </summary>
        public ICommand AskFirst { get; private set; }

        /// <summary>
        /// Gets the model associated with this view model.
        /// </summary>
        public CloseProgramsModel Model
        {
            get
            {
                return this.model;
            }
        }

        /// <summary>
        /// Sets the view associated with this view model.
        /// </summary>
        /// <param name="value">The view to be associated with this view model.</param>
        internal void AssignView(CloseProgramsView value)
        {
            this.view = value;
        }

        /// <summary>
        /// Closes or terminates the selected programs if the check box checkBoxCloseThesePrograms is checked.
        /// </summary>
        internal void DoStartPresentation()
        {
            if (this.model.Presenting)
            {
                return;
            }

            this.model.Presenting = true;
            this.view.IsEnabled = false;

            if (!this.model.CloseThesePrograms)
            {
                return;
            }

            const int OneSecond = 1000;
            foreach (ProgramToClose program in this.model.Programs)
            {
                program.Closed = ProgramsEnumerator.CloseProgram(program, OneSecond);
                if (program.Closed == false && this.model.KillIfCannotClose)
                {
                    if (!this.model.AskFirst || CloseProgramsViewModel.ConfirmKill(program))
                    {
                        ProgramsEnumerator.KillProcess(program);
                        program.Closed = true;
                    }
                }
            }
        }

        /// <summary>
        /// Starts the selected programs if the check box checkBoxCloseThesePrograms is checked.
        /// </summary>
        internal void DoStopPresentation()
        {
            if (!this.model.Presenting)
            {
                return;   
            }

            foreach (ProgramToClose program in this.model.Programs)
            {
                if (program.Closed == true)
                {
                    ProgramsEnumerator.OpenProgram(program);
                }
            }

            this.model.Presenting = false;
            this.view.IsEnabled = true;
        }

        /// <summary>
        /// Loads model state.
        /// </summary>
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters",
            MessageId = "GivingAPresentation.Commons.ExceptionHelper.Check(System.Boolean,System.String)",
            Justification = "Resources will be used in a future version.")]
        internal void DoInitializePart()
        {
            ExceptionHelper.Check(!this.model.Presenting, "Cannot load settings while giving a presentation.");

            try
            {
                this.model.Load();
            }
            catch (ConfigurationErrorsException e)
            {
                throw new GivingAPresentationException("Cannot load configuration.", e);
            }
        }

        /// <summary>
        /// Stores model state.
        /// </summary>                                                                   
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters",
            MessageId = "GivingAPresentation.Commons.ExceptionHelper.Check(System.Boolean,System.String)",
            Justification = "Resources will be used in a future version.")]
        internal void DoFinalizePart()
        {
            ExceptionHelper.Check(!this.model.Presenting, "Cannot save settings while giving a presentation.");

            try
            {
                this.model.Save();
            }
            catch (ConfigurationErrorsException e)
            {
                throw new GivingAPresentationException("Cannot save configuration.", e);
            }
        }

        /// <summary>
        /// Ask the user to kill a program that cannot be closed.
        /// </summary>
        /// <param name="program">The name of the program to kill.</param>
        /// <returns>Returns true if the user accepts to kill the program; false otherwise.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters",
            MessageId = "System.Windows.MessageBox.Show(System.String,System.String,System.Windows.MessageBoxButton," +
            "System.Windows.MessageBoxImage)", Justification = "Resources will be used in a future version.")]
        private static bool ConfirmKill(ProgramToClose program)
        {
            string message = String.Format(
                CultureInfo.CurrentCulture,
                "Cannot close {0}. Do you want to kill this program instead?", 
                program.FriendlyName);
            return MessageBox.Show(message, "Giving a presentation",  MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
        }

        /// <summary>
        /// Calls <code>SelectPrograms</code> dialog box to get a list of programs to close and adds selected programs to the given list box.
        /// </summary>
        /// <param name="listBox">The list box to add programs to.</param>
        private void DoAddRunningProgram(ListBox listBox)
        {
            SelectProgramsData data = new SelectProgramsData();
            data.ProgramsToClose = new List<ProgramToClose>();
            foreach (ProgramToClose program in this.model.Programs)
            {
                data.ProgramsToClose.Add(new ProgramToClose(program.ModuleFileName));
            }

            if (SelectPrograms.ShowDialog(null, data).GetValueOrDefault())
            {
                listBox.BeginInit();
                try
                {
                    foreach (ProgramToClose program in data.ProgramsToClose)
                    {
                        if (this.model.Programs.FirstOrDefault(item => item.ModuleFileName.Equals(program.ModuleFileName)) == null)
                        {
                            this.model.Programs.Add(program);
                        }
                    }
                }
                finally
                {
                    listBox.EndInit();
                }
            }
        }

        /// <summary>
        /// Removes a program from the list of programs in the given list box.
        /// </summary>
        /// <param name="listBox">The list box where the program is removed from.</param>
        private void DoRemoveProgram(ListBox listBox)
        {
            if (listBox.Items.Count == 0)
            {
                return;
            }

            if (listBox.SelectedItems == null)
            {
                return;
            }

            listBox.BeginInit();
            try
            {
                foreach (ProgramToClose program in listBox.SelectedItems)
                {
                    this.model.Programs.Remove(program);
                }
            }
            finally
            {
                listBox.EndInit();
            }
        }

        /// <summary>
        /// Opens a file open dialog and adds the file selected by the user to the list of programs and adds selected programs to the given
        /// list box.
        /// </summary>
        /// <param name="listBox">The list box to add programs to.</param>
        private void DoAddProgramFromFile(ListBox listBox)
        {
            OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
            dialog.DefaultExt = ".exe";
            dialog.Filter = "Programs (.exe)|*.exe";
            dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

            Nullable<bool> result = dialog.ShowDialog();

            if ((result == true) &&
                (this.model.Programs.FirstOrDefault(item => item.ModuleFileName.Equals(dialog.FileName)) == null))
            {
                listBox.BeginInit();
                try
                {
                    this.model.Programs.Add(new ProgramToClose(dialog.FileName));
                }
                finally
                {
                    listBox.EndInit();
                }
            }
        }
    }
}
